package morenoapp.components.thinkgraphv2;

import edu.umd.cs.piccolo.PCanvas;
import edu.umd.cs.piccolo.PNode;
import edu.umd.cs.piccolo.event.PDragSequenceEventHandler;
import edu.umd.cs.piccolo.event.PInputEvent;
import edu.umd.cs.piccolo.nodes.PText;
import edu.umd.cs.piccolo.util.PAffineTransform;
import java.awt.Color;
import java.awt.Cursor;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.geom.Point2D;
import java.awt.image.BufferedImage;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import javax.imageio.ImageIO;
import morenoapp.MorenoApp;
import morenoapp.components.thinkgraph.node.RespondentNode;
import morenoapp.components.thinkgraph.node.VectorNode;
import morenoapp.components.thinkgraph.node.WithTooltip;
import morenoapp.components.thinkgraphv2.nodes.CorrectionNode;
import morenoapp.components.thinkgraphv2.nodes.RelationshipItemNode;
import morenoapp.components.thinkgraphv2.nodes.RespondentItemNode;
import morenoapp.components.thinkgraphv2.nodes.SociogramNode;
import morenoapp.components.thinkgraphv2.nodes.VectorItemNode;
import morenoapp.model.data.MorenoAnswerFace;
import morenoapp.model.data.MorenoAnswerItemFace;
import morenoapp.model.data.MorenoModelFace;
import morenoapp.model.data.MorenoRespondentFace;
import morenoapp.model.listeners.MorenoModelAdapter;
import morenoapp.model.view2.MorenoView2Data;
import morenoapp.model.view2.MorenoView2Face;

/**
 *
 * @author dmitry.mamonov
 */
public class ThinkgraphCanvas extends PCanvas {
    /**
     * Конфигурация отображения социограммы (передаётся параметром).
     */
    MorenoView2Face config;
    /**
     * Преобразование переводяшее начало координат в левую верхнюю
     * точку видимой области.
     */
    PAffineTransform cameraNullTransform;
    /**
     * Узел для отображения основных элементов социограммы.
     */
    PNode graph;
    /**
     * Узел для отображения тултипов.
     */
    PText tooltipNode;
    /**
     * Флаг указывает что при следующем визуальном показе компонента
     * его следует заполить данными из модели.
     */
    boolean needToUpdate = true;
    /**
     * Социограмма (подложка картинки) - единственный объект.
     */
    SociogramNode sociogram;
    /**
     * Список узлов респондентов.
     */
    List<RespondentItemNode> respondentNodes = new ArrayList<RespondentItemNode>();
    /**
     * Список узлов связей между респондентами.
     */
    List<VectorItemNode> vectorNodes = new ArrayList<VectorItemNode>();
    public ThinkgraphCanvas(MorenoView2Face _config) {
        super();

        //remember passed configuration
        this.config = _config;

        //preserve and configure canvas environment
        cameraNullTransform = getCamera().getViewTransform();
        this.removeInputEventListener(this.getPanEventHandler());

        //configure base nodes
        sociogram = new SociogramNode(config);
        sociogram.setPaint(Color.yellow);
        getLayer().addChild(sociogram);

        graph = new PNode();
        graph.setPaint(null);
        getLayer().addChild(graph);



        tooltipNode = new PText();
        tooltipNode.setPickable(false);
        getCamera().addChild(tooltipNode);

        //listeners
        configureListeners();
    }

    /**
     * Добавляет всех необходимых слушателей.
     */
    protected void configureListeners() {
        //Слушатель модели для отслеживания изменений в ней.
        MorenoApp.getModel().addModelListener(new MorenoModelAdapter() {
            @Override
            public void onAnyElementChanged() {
                updateCanvasFromModel();
            }
        });

        config.addPropertyChangeListener(new PropertyChangeListener() {
            @Override
            public void propertyChange(PropertyChangeEvent evt) {
                System.out.printf(">> %s %s !> %s\n",
                        evt.getPropertyName(),
                        evt.getOldValue(),
                        evt.getNewValue());
                sociogram.propertyChange(evt);
                updateRespondentItemNodes(evt);
                updateVectorItemNodes(evt);
            }
        });


        //слушатель конфигурации канвы (параметров отображения).
//        config.addCanvasChangeListener(new MorenoViewChangeListener() {
//            @Override
//            public void onDisplayConfigChanged(String property, Object oldValue, Object newValue) {
//                if (config.isAccurateDraw()) {
//                    setDefaultRenderQuality(PPaintContext.HIGH_QUALITY_RENDERING);
//                } else {
//                    setDefaultRenderQuality(PPaintContext.LOW_QUALITY_RENDERING);
//                }
//            }
//
//        });

        //слушетель изменения конфигурации социограммы
        //TODO
//        config.addBackgroudChangeListener(new MorenoViewChangeListener() {
//            @Override
//            public void onDisplayConfigChanged(String property, Object oldValue, Object newValue) {
//                sociogram.onDisplayConfigChanged(property, oldValue, newValue);
//                updateVectorItemNodes(property, oldValue, newValue);
//            }
//
//        });

        //слушетель изменеия конфигурации узлов
        //TODO
//        config.addRespondentsChangeListener(new MorenoViewChangeListener() {
//            @Override
//            public void onDisplayConfigChanged(String property, Object oldValue, Object newValue) {
//                updateRespondentItemNodes(property, oldValue, newValue);
//                updateVectorItemNodes(property,oldValue,newValue);
//            }
//        });

        //слушатель изменения позиции респондентов
        //TODO
//        config.addRespondentPositionChangeListener(new MorenoViewChangeListener() {
//            @Override
//            public void onDisplayConfigChanged(String property, Object oldValue, Object newValue) {
//                updateVectorItemNodes(property, oldValue, newValue);
//                updateRespondentItemNodes(property, oldValue, newValue);
//            }
//        });

        //слушатель изменения конфигурации связей
        //TODO
//        config.addRelationshipsChangeListener(new MorenoViewChangeListener() {
//            @Override
//            public void onDisplayConfigChanged(String property, Object oldValue, Object newValue) {
//                updateVectorItemNodes(property, oldValue, newValue);
//            }
//        });

        //Слушатель инициирующий отображение тултипов.
        graph.addInputEventListener(new TooltipNodeDragHandler());
    }

    private void updateVectorItemNodes(PropertyChangeEvent evt) {
        for (VectorItemNode vector : vectorNodes) {
            vector.propertyChange(evt);
        }
    }

    private void updateRespondentItemNodes(PropertyChangeEvent evt) {
        for (RespondentItemNode resp : respondentNodes) {
            resp.propertyChange(evt);
        }
    }

    @Override
    public void paint(Graphics g) {
        if (needToUpdate) {
            updateCanvasFromModel();
        }
        //super.paintComponent(g);
        super.paint(g);
    }

    /**
     * Обновляет социограмму в соответствии с текущим состоянием модели.
     * Изменения применяются только в случае если компонет в настоящий
     * момент является видимым, в противном случае устанавливается
     * флаг что данные нужно будет перезагрузить при следующем показе объекта.
     */
    protected void updateCanvasFromModel() {
        if (isShowing() == true) {
            needToUpdate = false;

            graph.removeAllChildren();
            respondentNodes.clear();
            vectorNodes.clear();
            MorenoModelFace model = MorenoApp.getModel();
            for (MorenoRespondentFace resp : model.getRespondentsList()) {
                //add respondent node
                RespondentItemNode item = new RespondentItemNode(config, resp);
                respondentNodes.add(item);
                graph.addChild(item);

                //add correction node
                vectorNodes.add(new CorrectionNode(config, resp));

                //add relationships edges
                for (MorenoAnswerFace answer : resp.getAnswerList()) {
                    for (MorenoAnswerItemFace answerItem : answer.getAnswerItems()) {
                        if (answerItem.getTarget() != null) {
                            RelationshipItemNode rel = new RelationshipItemNode(config, answerItem);
                            vectorNodes.add(rel);
                        }
                    }
                }
            }
            for (VectorItemNode vNode : vectorNodes) {
                graph.addChild(vNode);
            }

            invalidate();
            validate();
        } else {
            needToUpdate = true;
        }
    }

    /**
     * This methos is specially overriden to support centered layout
     * of displayed elements.
     *
     * {@inheritDoc}
     */
    @Override
    public void setBounds(int x, int y, int w, int h) {
        getCamera().setViewTransform(cameraNullTransform);
        getCamera().translateView(w / 2, h / 2);
        super.setBounds(x, y, w, h);
    }

    public void saveAsImage(File targetFile) throws IOException {
        Dimension componentSize = this.getSize();
        BufferedImage img = new BufferedImage(componentSize.width,
                componentSize.height,
                BufferedImage.TYPE_INT_RGB);
        Graphics2D graphics = img.createGraphics();
        graphics.fillRect(0, 0, img.getWidth(), img.getHeight());
        this.paint(graphics);
        ImageIO.write(img, "png", targetFile); //NOI18N
    }

    /**
     * Node handler to display tooltip messages.
     */
    class TooltipNodeDragHandler extends PDragSequenceEventHandler {
        public TooltipNodeDragHandler() {
            getEventFilter().setMarksAcceptedEventsAsHandled(true);
        }

        @Override
        public void mouseEntered(PInputEvent _event) {
            PNode node = _event.getPickedNode();
            if (!_event.isLeftMouseButton()) {
                if (checkNodeType(node, RespondentNode.class)) {
                    node.setPaint(new Color(0xffff88));
                } else if (checkNodeType(node, VectorNode.class)) {
                    setCursor(new Cursor(Cursor.DEFAULT_CURSOR));
                }
            }
            updateToolTip(_event);
        }

        @Override
        public void mouseExited(PInputEvent _event) {
            PNode node = _event.getPickedNode();
            if (!_event.isLeftMouseButton()) {
                if (checkNodeType(node, RespondentNode.class)) {
                    node.setPaint(null);
                } else if (checkNodeType(node, VectorNode.class)) {
                    setCursor(new Cursor(Cursor.CROSSHAIR_CURSOR));
                }
            }
            updateToolTip(null);
        }

        @Override
        public void drag(PInputEvent _event) {
            PNode node = _event.getPickedNode();
            while (node != null) {
                if (node instanceof RespondentItemNode) {
                    node.translate(_event.getDelta().width, _event.getDelta().height);
                    break;
                }
                node = node.getParent();
            }
            updateToolTip(_event);
        }

        private boolean checkNodeType(PNode _node, Class<?> _type) {
            PNode current = _node;
            while (current != null) {
                if (_type.isAssignableFrom(current.getClass())) {
                    return true;
                }
                current = current.getParent();
            }
            return false;
        }

        private void updateToolTip(PInputEvent _event) {
            String tooltipString = null;
            if (_event != null && config.isDisplayTooltips()) {
                PNode node = _event.getInputManager().
                        getMouseOver().
                        getPickedNode();
                while (node != null) {
                    //System.out.println("trace for tooltip: " + node);
                    if (node instanceof WithTooltip) {
                        tooltipString = ((WithTooltip) node).getTooltipText();
                        break;
                    }
                    node = node.getParent();
                }
                //TODO - странный код, не понятно как он работает
                Point2D p = _event.getCanvasPosition();
                p = _event.getPath().
                        canvasToLocal(p, getCamera());
                tooltipNode.setOffset(p.getX() + 12, p.getY() - 23);
            }

            tooltipNode.setText(tooltipString);
        }

    }
}



